#lang racket

;Put all requires here for consistency
(require racket/system)
(require openssl)

;Represents a tunnel
(struct tunnel (
                type           ;type of tunnel
                lport          ;local port
                rhost          ;remote host
                rport          ;remote port
                listener       ;listener
                _bytesbox
                _totbytesbox
                _lasttimebox
                _destructor
                )
  #:transparent)

;Command substitution function
(define $
  (λ x
    (define args (string-join x))
    (string-trim
     (with-output-to-string
      (thunk
       (system args))))))

;tcpclient struct
(struct tcpclient (input output))

;Accept a tcpclient
(define (tcp-accept-client listener)
  (define-values (in out) (tcp-accept listener))
  (tcpclient in out))

;Connect and return a tcpclient
(define (tcp-connect-client hostname port)
  (define-values (in out) (tcp-connect hostname port))
  (tcpclient in out))

;Connect and return a tcpclient, from an ssl connection
(define (ssl-connect-client hostname port (type 'tls))
  (define-values (sin sout) (tcp-connect hostname port))
  (define-values (in out) (ports->ssl-ports sin sout 
                                            #:mode 'connect
                                            #:encrypt type
                                            #:shutdown-on-close? #t))
  (tcpclient in out))

(define (tcpclient-close clnt)
  (close-input-port (tcpclient-input clnt))
  (close-output-port (tcpclient-output clnt)))

;Copies port p1 to p2, updating byte count in bytesbox
(define (my-copy-port p1 p2 callback)
  (cond
    [(port-closed? p1) (void)]
    [(port-closed? p2) (void)]
    [else
     (define buffer (make-bytes 65536))
     (define c (read-bytes-avail! buffer p1))
     (cond
       [(eof-object? c) (void)]
       [else 
        (write-bytes buffer p2 0 c)
        (flush-output p2)
        ;Updates the byte count box
        (callback c)
        (my-copy-port p1 p2 callback)
        ])]))

;Dead loop
(define (forever)
  (sleep 1)
  (forever))

;Timeout
(define (timeout tnk timeout)
  (with-handlers ((exn? (λ(e) #f)))
    (define toret #f)
    (thread (λ() (set! toret (tnk))))
    (sleep (/ timeout 1000))
    (if toret toret #f)))

(provide (all-defined-out))